#ifndef GRAY_H_INCLUDED
#define GRAY_H_INCLUDED
/******************************************************************************
* (c)2020-2025 Michael T. Richter
*
* This software is distributed under the terms of WTFPLv2.  The full terms and
* text of the license can be found at http://www.wtfpl.net/txt/copying
******************************************************************************/

/** @file
 *  @brief Implementation of a simple gray encoder/decoder pair
 *
 *  This library provide a simple gray encoder and decoder pairing.  The size
 *  of the gray code storage type is user adjustable, defaulting to a 64-bit
 *  unsigned integer (which is also the maximum size the provided code can
 *  support).  There is usually no need to adjust this unless space is REALLY
 *  at a premium in your code.
 */

#include <limits.h>
#include <stdint.h>

/**** CONFIGURATION - user-adjustable parameters go here *********************/
/* Note: -DDEFAULT_GRAY_TYPE=<type> in the command line works too.           */
#ifdef DEFAULT_GRAY_TYPE
typedef DEFAULT_GRAY_TYPE gray_t;
typedef DEFAULT_GRAY_TYPE uint_t;
#else
typedef uint64_t gray_t;
typedef uint64_t uint_t;
#endif
#define STATIC_ASSERT_CONCAT_(a,b) a##b
#define STATIC_ASSERT_CONCAT(a,b) STATIC_ASSERT_CONCAT_(a,b)
#define STATIC_ASSERT(expr) \
    typedef char STATIC_ASSERT_CONCAT(static_assert_failed_, __LINE__)[(expr) ? 1 : -1]
STATIC_ASSERT(
    sizeof(gray_t) * CHAR_BIT == 8  ||
    sizeof(gray_t) * CHAR_BIT == 16 ||
    sizeof(gray_t) * CHAR_BIT == 32 ||
    sizeof(gray_t) * CHAR_BIT == 64 ||
    sizeof(gray_t) * CHAR_BIT == 128||
    sizeof(gray_t) * CHAR_BIT == 256||
    sizeof(gray_t) * CHAR_BIT == 512
);
/*****************************************************************************/

/*! Convert an unsigned integer to its equivalent gray encoding.
 *
 *  @param[in]  value   An unsigned value to be converted.
 *
 *  @return             The gray encoding of `value`.
 */
gray_t uint_to_gray(uint_t);

/*! Convert a gray encoding back to an unsigned integer.
 *
 *  @param[in]  gray    A gray-encoded value to be restored.
 *
 *  @return             The unsigned value restored.
 */
uint_t gray_to_uint(gray_t);

#ifdef GRAY_IMPLEMENTATION
/*******************************************************************************
* GRAY IMPLEMENTATION SECTION
* ==============================
*
* In a single source file in your project, the symbol GRAY_IMPLEMENTATION
* must be defined before including the header:
*
*   ```c
*   #define GRAY_IMPLEMENTATION
*   #include "gray.h"
*   ```
*
* All other source files using this library must *not* define this symbol.
*******************************************************************************/

#include <error.h>
#include <stdlib.h>

#include "gray.h"

gray_t uint_to_gray(uint_t n)
{
    return n ^ (n >> 1);
}

uint_t gray_to_uint(gray_t g)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
    static const uint_t bits = sizeof(gray_t) * CHAR_BIT;
#pragma GCC diagnostic pop

#pragma GCC diagnostic ignored "-Wshift-count-overflow"
#pragma GCC diagnostic ignored "-Wswitch-outside-range"

    switch (bits)
    {
    // fall-through is deliberate
    case 512:
        g ^= g >> 256;

    case 256:
        g ^= g >> 128;

    case 128:
        g ^= g >> 64;

    case 64:
        g ^= g >> 32;

    case 32:
        g ^= g >> 16;

    case 16:
        g ^= g >> 8;

    case 8:
        g ^= g >> 4;
        g ^= g >> 2;
        g ^= g >> 1;
        break;

    default:
        // we statically confirm supported bit sizes
        break;
    }

#pragma GCC diagnostic pop

    return g;
}
#endif //GRAY_IMPLEMENTATION

#endif //GRAY_H_INCLUDED
